﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;

namespace FR.CodeVisualizer.ObjectSource
{
    public static class Extensions
    {
        public static List<Entity> DumpMe(this Object o, int depth = 2)
        {
            string filename = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CodeVisualizer", "CodeVisualizerOptions.xml");

            if (File.Exists(filename))
            {
                XDocument doc = XDocument.Load(filename);
                XElement xe = doc.Descendants("Depth").FirstOrDefault();
                if (xe != null)
                    int.TryParse(xe.Value, out depth);
            }

            return Dump.ReadObject(o, depth).entities;
        }
    }

    [Serializable]
    public class Entity
    {
        public Guid Pk = Guid.NewGuid();

        public string Name { get; set; }

        List<EntityProperties> properties;
        public List<EntityProperties> Properties
        {
            get
            {
                if (properties == null)
                    properties = new List<EntityProperties>();
                return properties;
            }
            set { properties = value; }
        }

        List<Entity> nestedEntities;
        public List<Entity> NestedEntities
        {
            get
            {
                if (nestedEntities == null)
                    nestedEntities = new List<Entity>();
                return nestedEntities;
            }
            set { nestedEntities = value; }
        }
    }

    [Serializable]
    public class EntityProperties
    {
        public string PropertyName { get; set; }
        public object Value { get; set; }
        public string PropertyType { get; set; }
    }

    public class Dump
    {
        public static Dump ReadObject(object o, int depth)
        {
            Dump dumper = new Dump(depth);
            dumper.WriteObject(o, null);

            return dumper;
        }

        public List<Entity> entities { get; private set; }
        int level;
        int depth;

        private Dump(int depth)
        {
            this.entities = new List<Entity>();
            this.depth = depth;
        }

        /// <summary>
        /// Reads the object o using reflection and stores the values in a list of Entity
        /// </summary>
        /// <param name="o"></param>
        /// <param name="currentEntity"></param>
        private void WriteObject(object o, Entity currentEntity)
        {
            if (o.GetType().BaseType == typeof(System.Data.Objects.DataClasses.EntityReference))
                return;

            if (o is IEnumerable)
            {
                foreach (object element in (IEnumerable)o)
                {
                    if (element is IEnumerable && !(element is string))
                    {
                        if (level < depth)
                        {
                            level++;
                            WriteObject(element, currentEntity);
                            level--;
                        }
                    }
                    else
                        WriteObject(element, currentEntity);
                }
            }
            else
            {
                //reads the object members
                MemberInfo[] members = o.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);

                Entity newEntity = new Entity();
                newEntity.Name = o.ToString();

                //we are only interested in properties and fields
                foreach (MemberInfo m in members.Where(p => p is FieldInfo || p is PropertyInfo))
                {
                    FieldInfo f = m as FieldInfo;
                    PropertyInfo p = m as PropertyInfo;
                    if (f != null || p != null)
                    {
                        Type t = f != null ? f.FieldType : p.PropertyType;

                        //reads the properties values, name value and type
                        EntityProperties property = new EntityProperties();
                        property.PropertyName = m.Name;
                        property.PropertyType = t.FullName;

                        if (t.IsValueType || t == typeof(string))
                        {
                            try
                            {
                                //reads the value
                                property.Value = f != null ? f.GetValue(o) : p.GetValue(o, null);
                                newEntity.Properties.Add(property);
                            }
                            catch { }
                        }
                    }
                }

                //add to new entity or as a nested entity
                if (currentEntity == null)
                    entities.Add(newEntity);
                else
                    currentEntity.NestedEntities.Add(newEntity);

                //where we read the complex properties until the defined depth
                if (level < depth)
                {
                    foreach (MemberInfo m in members.Where(p => p is FieldInfo || p is PropertyInfo))
                    {
                        FieldInfo f = m as FieldInfo;
                        PropertyInfo p = m as PropertyInfo;
                        if (f != null || p != null)
                        {
                            Type t = f != null ? f.FieldType : p.PropertyType;
                            if (!(t.IsValueType || t == typeof(string)))
                            {
                                try
                                {
                                    object value = f != null ? f.GetValue(o) : p.GetValue(o, null);
                                    if (value != null)
                                    {
                                        level++;
                                        WriteObject(value, newEntity);
                                        level--;
                                    }
                                }
                                catch { }
                            }
                        }
                    }
                }
            }
        }
    }
}
